<template>
    <BFormRadioGroup class="tnxbsv-select tnxbsv-radio-group"
        v-model="model"
        :options="items"
        :value-field="valueName"
        :text-field="textName"
        :buttons="selector === 'radio-button'"
        button-variant="outline-primary"
        v-if="items && (selector==='radio' || selector === 'radio-button')"/>
    <BDropdown class="tnxbsv-select tnxbsv-dropdown"
        :key="groupKey"
        :text="currentText"
        :variant="theme"
        :size="size"
        v-else-if="selector==='dropdown'">
        <BDropdownItem class="empty-item" :active="isSelected(emptyValue)" @click="select(emptyValue)" v-if="empty">
            <span>{{ emptyText || '&nbsp;' }}</span>
        </BDropdownItem>
        <template v-if="items">
            <BDropdownItem v-for="(item, index) of items" :key="index"
                :active="isSelected(item[valueName])"
                :disabled="item.disabled"
                @click="select(item[valueName])"
            >
                <slot name="option" :item="item" v-if="$slots.option"></slot>
                <template v-else>
                    <i :class="item[iconName]" v-if="item[iconName]"></i>
                    <span>{{ item[textName] }}</span>
                </template>
            </BDropdownItem>
        </template>
        <BDropdownItem v-else>
            <LoadingIcon/>
        </BDropdownItem>
    </BDropdown>
    <BFormSelect class="tnxbsv-select"
        :class="{'is-empty': model === emptyValue}"
        v-model="model"
        :key="groupKey"
        :variant="theme"
        :options="items"
        :value-field="valueName"
        :text-field="textName"
        :size="size"
        v-else-if="items">
        <template #first v-if="empty">
            <BFormSelectOption class="empty-item" :value="emptyValue">{{ emptyText || '&nbsp;' }}</BFormSelectOption>
        </template>
        <template #option="{value, text}">
            <slot name="option" :item="getItem(value)" v-if="$slots.option"></slot>
            <template v-else>
                <i :class="getItem(value)[iconName]" v-if="getItem(value)[iconName]"></i>
                <span>{{ text }}</span>
            </template>
        </template>
    </BFormSelect>
    <div class="flex-v-center" v-else>
        <LoadingIcon/>
    </div>
</template>

<script>
import {BFormRadioGroup, BDropdown, BDropdownItem, BFormSelect, BFormSelectOption} from 'bootstrap-vue-next';
import LoadingIcon from '../loading-icon/LoadingIcon.vue';

export const isMultiSelector = function (selector) {
    return selector === 'checkbox' || selector === 'tags' || selector === 'multi-select' || selector === 'texts';
}
export default {
    name: 'TnxbsvSelect',
    components: {BFormRadioGroup, BDropdown, BDropdownItem, BFormSelect, BFormSelectOption, LoadingIcon},
    props: {
        id: [Number, String],
        modelValue: {
            type: [String, Number, Boolean, Array],
            default: null,
        },
        selector: String,
        items: Array,
        valueName: {
            type: String,
            default: 'value',
        },
        textName: {
            type: String,
            default: 'text',
        },
        indexName: {
            type: String,
            default: 'index',
        },
        iconName: {
            type: String,
            default: 'icon',
        },
        defaultValue: {
            type: [String, Number, Boolean, Array],
            default: null,
        },
        empty: {
            type: [Boolean, String],
            default: false,
        },
        emptyValue: {
            type: [String, Number, Boolean, Array],
            default: null,
        },
        emptyClass: String,
        disabled: Boolean,
        tagClick: Function, // 点击一个标签选项时调用，如果返回false，则选项不会被选中
        change: Function, // 选中值变化后的事件处理函数，比change事件传递更多参数数据
        filterable: Boolean,
        theme: {
            type: String,
            default: 'primary',
        },
        size: String,
        border: Boolean,
    },
    emits: ['update:modelValue', 'change'],
    data() {
        let model = this.getModel(this.items);
        if (this.items && model !== this.modelValue) {
            this.$emit('update:modelValue', model);
        }
        return {
            groupKey: new Date().getTime(),
            model: model,
            hiddenValues: [],
        };
    },
    computed: {
        emptyText() {
            return typeof this.empty === 'string' ? this.empty : '';
        },
        currentText() {
            let item = this.getItem(this.model);
            return item ? item[this.textName] : this.emptyText;
        },
        modelEqualsModelValue() {
            if (Array.isArray(this.model) && Array.isArray(this.modelValue)) {
                return this.model.equals(this.modelValue);
            } else {
                return this.model === this.modelValue;
            }
        },
        firstEnabledItem() {
            for (let item of this.items) {
                if (!item.disabled) {
                    return item;
                }
            }
            return undefined;
        },
    },
    watch: {
        model(newValue, oldValue) {
            if (!this.modelEqualsModelValue) {
                this.$emit('update:modelValue', newValue);
                // 新旧值不同时为空才触发变更事件
                const util = window.tnx.util;
                if (util.object.isNotEmpty(newValue) || util.object.isNotEmpty(oldValue)) {
                    let vm = this;
                    // 确保变更事件在值变更应用后再触发
                    this.$nextTick(() => {
                        vm.triggerChange(newValue);
                    });
                }
            }
        },
        modelValue() {
            this.model = this.getModel(this.items);
        },
        items(items) {
            this.model = this.getModel(items);
        },
    },
    methods: {
        isMulti() {
            return isMultiSelector(this.selector);
        },
        triggerChange(value) {
            if (this.change) {
                let item = undefined;
                if (this.isMulti()) {
                    item = [];
                    if (Array.isArray(value)) {
                        for (let v of value) {
                            item.push(this.getItem(v));
                        }
                    }
                } else {
                    item = this.getItem(value);
                }
                this.change(item, this.id);
            } else {
                this.$emit('change', value);
            }
        },
        getItem(value) {
            if (this.empty && value === this.emptyValue) {
                let item = {};
                item[this.valueName] = this.emptyValue;
                item[this.textName] = this.emptyText;
                return item;
            }
            if (value !== undefined && value !== null && this.items) {
                for (let item of this.items) {
                    if ((item[this.valueName] + '') === (value + '')) {
                        return item;
                    }
                }
            }
            return undefined;
        },
        getText(value) {
            let item = this.getItem(value);
            return item ? item[this.textName] : undefined;
        },
        getModel(items) {
            const util = window.tnx.util;
            let model = this.modelValue;
            if (util.object.isNull(model)) {
                model = this.defaultValue;
            }
            if (this.isMulti()) { // 多选时需确保值为数组
                if (util.object.isNull(model)) {
                    return [];
                }
                if (!Array.isArray(model)) {
                    model = [model];
                }
                if (items && items.length) {
                    // 多选时，如果model中有值不在items中，则去掉
                    for (let i = model.length - 1; i >= 0; i--) {
                        let item = this.getItem(model[i]);
                        if (!item) {
                            model.splice(i, 1);
                        }
                    }
                } else {
                    model = [];
                }
                return model;
            }
            if (util.object.isNull(model)) {
                return null;
            }
            if (items && items.length) {
                let item = this.getItem(model);
                if (item) {
                    return item[this.valueName];
                } else { // 如果当前值找不到匹配的选项，则需要考虑是设置为空还是默认选项
                    if (!this.empty) { // 如果不能为空，则默认选中第一个可用选项
                        let firstItem = this.firstEnabledItem;
                        if (firstItem && firstItem[this.valueName]) {
                            return firstItem[this.valueName];
                        }
                    }
                }
            }
            return model;
        },
        filter(keyword) {
            for (let item of this.items) {
                let itemValue = item[this.valueName];
                let hiddenIndex = this.hiddenValues.indexOf(itemValue);
                if (this.matchesItem(item, keyword)) {
                    if (hiddenIndex >= 0) { // 匹配且原本已隐藏的，则取消隐藏
                        this.hiddenValues.splice(hiddenIndex, 1);
                    }
                } else {
                    if (hiddenIndex < 0) { // 不匹配且原本未隐藏的，则进行隐藏
                        this.hiddenValues.push(itemValue);
                    }
                }
            }
        },
        matchesItem(item, keyword) {
            return !keyword || window.tnx.util.string.matchesForEach(item[this.valueName], keyword)
                || window.tnx.util.string.matchesForEach(item[this.textName], keyword)
                || window.tnx.util.string.matchesForEach(item[this.indexName], keyword)
        },
        isSelected(value) {
            if (Array.isArray(this.model)) {
                return this.model.contains(value);
            } else {
                return this.model === value;
            }
        },
        select(value, event) {
            if (this.tagClick) {
                let item = this.getItem(value);
                if (item) {
                    if (this.tagClick(item) === false) {
                        return;
                    }
                }
            }
            if (this.isMulti()) {
                let index = this.model.indexOf(value);
                if (index >= 0) {
                    this.model = this.model.filter((e, i) => {
                        return i !== index;
                    });
                } else {
                    this.model = this.model.concat([value]);
                }
            } else {
                this.model = value;
            }
            if (event) {
                event.currentTarget.blur();
            }
        },
        clear() {
            if (this.isMulti()) {
                this.model = [];
            } else {
                this.model = null;
            }
        },
        disableItem(itemValue, disabled, reverseOther) {
            let values = Array.isArray(itemValue) ? itemValue : [itemValue];
            for (let item of this.items) {
                if (values.contains(item[this.valueName])) {
                    item.disabled = disabled;
                    // 如果禁用的选项已被选择，则需从已选择值中移除该选项的值
                    if (this.isMulti()) {
                        if (this.model.includes(itemValue)) {
                            this.model.remove(itemValue);
                        }
                    } else {
                        if (this.model === itemValue) {
                            this.model = undefined;
                        }
                    }
                } else if (reverseOther) {
                    item.disabled = !disabled;
                }
            }
            this.groupKey = new Date().getTime();
        },
    }
}
</script>

<style>
.tnxbsv-select[variant="outline-danger"] {
    border-color: var(--bs-danger-border-subtle);
    color: var(--bs-danger);
}

.tnxbsv-select[variant="outline-danger"]:focus {
    box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
}

.tnxbsv-radio-group.btn-group > .btn {
    flex: none;
    --bs-btn-padding-x: 1rem;
}

select.tnxbsv-select option:disabled {
    color: var(--bs-tertiary-color);
}

select.tnxbsv-select.is-empty,
.tnxbsv-select .empty-item {
    color: var(--bs-tertiary-color);
}

select.tnxbsv-select.is-empty option:not(.empty-item):not(:disabled) {
    color: var(--bs-body-color);
}
</style>
